package biback.domain.customer

import biback.domain._
import biback.domain.account._
import biback.domain.product.BaseProduct.{CurrentDeposit1st, CurrentDeposit2nd}
import biback.domain.product.Limits.{DayOutTrnAmtSumMax, LimitKey, LimitType, QuantityMax}
import biback.domain.product._
import biback.utils._
import biback.domain.product.ProductParam._

case class PersonalCustomer(
                             customer: Personal,
                             accounts: Map[AccountId, Account] = Map.empty[AccountId, Account]
                           ) extends DomainObject {

  import BalanceChangeType._
  import BalanceType._

  def onCommand(c: IdValidate): CommandResult[c.ReplyType, IdValidated] = customer.onCommand(c)

  def onEvent(e: IdValidated): PersonalCustomer = copy(customer = customer.onEvent(e))

  def createSubAcctNo(product: BaseProduct, n: Int = 0): ErrOr[SubAccountNo] =
    for {
      _ <- n < 100 ?= ProductQuantityLimited(product, 100)
      no <- (product.code.value + s"0$n".takeRight(2)).check[SubAccountNoRule]
    } yield no

  def onCommand(c: OpenAccount): CommandResult[c.ReplyType, AccountOpened] =
    for {
      _ <- openCheck(c.product.base, c.param)
      n <- createSubAcctNo(c.product.base)
    } yield AccountOpened(c.ctx, c.acctId, c.acctNo, n, c.product, c.ctx.quota.brcNo)

  def onCommand(c: OpenSubAccount): CommandResult[c.ReplyType, SubAccountOpened] =
    for {
      a <- foundAccount(c.acctId)
      _ <- openCheck(c.product.base, c.param)
      n <- createSubAcctNo(c.product.base, a.subs.length)
    } yield SubAccountOpened(c.ctx, c.acctId, n, c.product, c.ctx.quota.brcNo)

  def onEvent(e: AccountOpened): PersonalCustomer = {
    val a = Account(e)
    copy(accounts = accounts.updated(a.id, a))
  }

  def onEvent(e: SubAccountOpened): PersonalCustomer = {
    val a = accounts.get(e.acctId).map(_.onEvent(e)).get
    copy(accounts = accounts.updated(a.id, a))
  }

  def onCommand(c: CashDeposit): CommandResult[c.ReplyType, BalanceChanged] =
    for {
      a <- foundAccount(c.acctId)
      b <- a.foundSub(c.subAcctNo)
      _ <- isType[DepositProduct](b.product.base, NonDepositProduct)
      inputs = new MinMaxValuesSetter with IdValidatedSetter {
        val key = DayOutTrnAmtSumMax(c.ctx.date)
        val minMaxValues = Map[LimitKey, BiDec](
          key -> (b.trnAmtSums.getOrElse(key, BiDec0) + c.trnAmt)
        )
        val idValidated: IdValidationType = a.idValidated
      }
      _ <- depositCheck(b.product.base, c.param, inputs)
      u <- b += AccountBalance
    } yield BalanceChanged(c.ctx, c.acctId, c.subAcctNo, u.balances, `+`, c.trnAmt)

  def onCommand(c: CashWithdraw): CommandResult[c.ReplyType, BalanceChanged] =
    for {
      a <- foundAccount(c.acctId)
      b <- a.foundSub(c.subAcctNo)
      inputs = new MinMaxValuesSetter with IdValidatedSetter {
        val key = DayOutTrnAmtSumMax(c.ctx.date)
        val minMaxValues = Map[LimitKey, BiDec](
          key -> (b.trnAmtSums.getOrElse(key, BiDec0) + c.trnAmt)
        )
        val idValidated: IdValidationType = a.idValidated
      }
      _ <- withdrawCheck(b.product.base, c.param, inputs)
      u <- b += AccountBalance
    } yield BalanceChanged(c.ctx, c.acctId, c.subAcctNo, u.balances, `-`, c.trnAmt)

  def onCommand(c: CalcInterest): CommandResult[c.ReplyType, BalanceChanged] =
    for {
      a <- foundAccount(c.acctId)
      b <- a.foundSub(c.subAcctNo)
      u <- b += AccountBalance
    } yield BalanceChanged(c.ctx, c.acctId, c.subAcctNo, u.balances, `+`, c.intAmt)

  def onEvent(e: BalanceChanged): PersonalCustomer = {
    val a = accounts.get(e.acctId).map(_.onEvent(e)).get
    copy(accounts = accounts.updated(a.id, a))
  }

  def foundAccount(acctId: AccountId) =
    accounts.get(acctId).toRight(AccountNotFound(acctId))

  def foundSubAccount(acctId: AccountId, subAcctNo: SubAccountNo) =
    foundAccount(acctId).flatMap(_.foundSub(subAcctNo))

  def countAccount(p: BaseProduct): Int =
    accounts.values.count(_.subs.find(_.product.base == p).nonEmpty)

  def openCheck(product: BaseProduct, param: ProductParam): ErrOrNone = {
    val inputs = new CustomerStatusSetter with MinMaxValuesSetter {
      val customerStatus = customer.status
      val minMaxValues = Map[LimitKey, BiDec](
        QuantityMax(LimitType.CurrentDeposit1stNum) -> (countAccount(product) + 1)
      )
    }
    product match {
      case CurrentDeposit1st => CurrentDeposit1st.open(param, inputs)
      case CurrentDeposit2nd => CurrentDeposit2nd.open(param, inputs)
    }
  }

  def depositCheck(product: BaseProduct, param: ProductParam, inputs: MinMaxValuesSetter with IdValidatedSetter): ErrOrNone = {
    product match {
      case CurrentDeposit1st => CurrentDeposit1st.deposit(param, inputs)
      case CurrentDeposit2nd => CurrentDeposit2nd.deposit(param, inputs)
    }
  }
  def withdrawCheck(product: BaseProduct, param: ProductParam, inputs: MinMaxValuesSetter with IdValidatedSetter): ErrOrNone = {
    product match {
      case CurrentDeposit1st => CurrentDeposit1st.withdraw(param, inputs)
      case CurrentDeposit2nd => CurrentDeposit2nd.withdraw(param, inputs)
    }
  }
}